﻿using System.Globalization;
using System.Linq.Dynamic.Core;
using System.Linq.Expressions;
using Mapster;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.DynamicLinq;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;

namespace Devonline.AspNetCore;

/// <summary>
/// 公共数据处理服务
/// </summary>
/// <typeparam name="TDbContext">数据库上下文</typeparam>
/// <typeparam name="TKey">键类型</typeparam>
public class ServiceBase<TDbContext, TKey> : IServiceBase<TDbContext, TKey> where TDbContext : DbContext where TKey : IConvertible
{
    protected readonly TDbContext _context;
    protected readonly ILogger<ServiceBase<TDbContext, TKey>> _logger;
    protected readonly IDistributedCache _cache;
    protected readonly AppSetting _appSetting;
    protected readonly HttpContext _httpContext;
    protected readonly HttpRequest _request;
    protected readonly string? _userName;
    protected bool _enableCache;

    /// <summary>
    /// 构造方法
    /// </summary>
    /// <param name="logger">日志</param>
    /// <param name="context">数据库上下文</param>
    /// <param name="httpContextAccessor">Http上下文</param>
    /// <param name="cache">缓存</param>
    /// <param name="appSetting">配置</param>
    public ServiceBase(
        TDbContext context,
        ILogger<ServiceBase<TDbContext, TKey>> logger,
        IDistributedCache cache,
        IHttpContextAccessor httpContextAccessor,
        AppSetting appSetting
        )
    {
        if (httpContextAccessor.HttpContext == null)
        {
            throw new ArgumentNullException(nameof(httpContextAccessor.HttpContext), "HttpContext can't be null!");
        }

        _context = context;
        _logger = logger;
        _cache = cache;
        _appSetting = appSetting;
        _httpContext = httpContextAccessor.HttpContext;
        _request = _httpContext.Request;
        _userName = _httpContext.User.Identity?.Name;
        _enableCache = _appSetting.EnableCache;
    }

    /// <summary>
    /// Gets or sets a flag indicating if changes should be persisted after CreateAsync, 
    /// UpdateAsync and DeleteAsync are called.
    /// True if changes should be automatically persisted, otherwise false.
    /// </summary>
    public bool AutoSaveChanges { get; set; } = true;
    #region 基础方法
    /// <summary>
    /// 从 httpContext 获取用户标识, 用户标识在 User.Claims 中的 type 为 sub; 在 request 中为 userId
    /// </summary>
    /// <returns></returns>
    public virtual string? GetUserId() => _httpContext.GetUserId();
    /// <summary>
    /// 获取附件绝对地址
    /// </summary>
    /// <param name="fileName">附件文件名</param>
    /// <returns></returns>
    public virtual string GetAttachmentPath(string fileName) => Path.Combine((_appSetting.Attachment?.RootPath ?? AppSettings.DEFAULT_ATTACHMENT_PATH).GetAbsolutePath(), fileName);
    /// <summary>
    /// 获取 fileName 同扩展名临时文件名
    /// </summary>
    /// <param name="fileName"></param>
    /// <returns></returns>
    public virtual string GetTempFileName(string fileName, string? extension = null)
    {
        var filePath = DateTime.UtcNow.ToString(AppSettings.DEFAULT_DATE_FORMAT, CultureInfo.InvariantCulture);
        var attachmentPath = GetAttachmentPath(filePath);
        if (!Directory.Exists(attachmentPath))
        {
            Directory.CreateDirectory(attachmentPath);
        }

        return Path.Combine(filePath, KeyGenerator.GetKey() + (extension ?? Path.GetExtension(fileName)));
    }
    /// <summary>
    /// 获取查询选项列表
    /// </summary>
    /// <returns>返回合并后的查询选项</returns>
    public virtual IEnumerable<KeyValuePair<string, StringValues>> GetRequestOptions()
    {
        var queryString = _request.Query.ToList();
        if (_request.HasFormContentType && (_request.Form.Keys?.Count ?? 0) > 0)
        {
            queryString.AddRange(_request.Form);
        }

        if (_request.Headers.IsNotNullOrEmpty())
        {
            queryString.AddRange(_request.Headers);
        }

        if (_request.Cookies.IsNotNullOrEmpty())
        {
            queryString.AddRange(_request.Cookies.ToDictionary(x => x.Key, x => new StringValues(x.Value)));
        }

        return queryString;
    }
    /// <summary>
    /// 从当前 request 中获取参数的值, 取值顺序 Query -> Form -> Header -> Cookie
    /// </summary>
    /// <param name="key"></param>
    /// <returns></returns>
    public virtual T? GetRequestOption<T>(string key) where T : IConvertible
    {
        var value = string.Empty;
        if (_request.Query.Keys.Contains(key))
        {
            value = _request.Query[key];
        }
        else if (_request.HasFormContentType && _request.Form.Keys.IsNotNullOrEmpty() && _request.Form.Keys.Contains(key))
        {
            value = _request.Form[key];
        }
        else if (_request.Headers.IsNotNullOrEmpty() && _request.Headers.ContainsKey(key))
        {
            value = _request.Headers[key];
        }
        else if (_request.Cookies.IsNotNullOrEmpty() && _request.Cookies.Keys.Contains(key))
        {
            value = _request.Cookies[key];
        }

        return string.IsNullOrWhiteSpace(value) ? default : value.To<T>();
    }
    /// <summary>
    /// 获取上下文对象中参数/变量的值, 取值顺序 HttpContext -> Query -> Form -> Header -> Cookie
    /// </summary>
    /// <param name="key"></param>
    /// <returns></returns>
    public virtual T? GetContextOption<T>(string key) where T : IConvertible
    {
        var value = _httpContext.User.Claims.FirstOrDefault(c => c.Type == key)?.Value;
        return string.IsNullOrWhiteSpace(value) ? _httpContext.Request.GetRequestOption<T>(key) : value.To<T>();
    }
    /// <summary>
    /// 调用实例的 Create 方法为基础字段赋值
    /// </summary>
    /// <typeparam name="TEntitySet">查询的类型</typeparam>
    /// <param name="entitySet"></param>
    public virtual void Create<TEntitySet>(TEntitySet entitySet)
    {
        if (entitySet is IEntitySetWithCreate<TKey> entitySetWithCreate)
        {
            entitySetWithCreate.Create(_userName);
        }
    }
    /// <summary>
    /// 调用实例的 Update 方法为基础字段赋值
    /// </summary>
    /// <typeparam name="TEntitySet">查询的类型</typeparam>
    /// <param name="entitySet"></param>
    public virtual void Update<TEntitySet>(TEntitySet entitySet)
    {
        if (entitySet is IEntitySetWithCreateAndUpdate<TKey> entitySetWithCreateAndUpdate)
        {
            entitySetWithCreateAndUpdate.Update(_userName);
        }
    }
    /// <summary>
    /// 提交当前操作
    /// </summary>
    /// <returns></returns>
    public virtual Task<int> SaveChangesAsync() => _context.SaveChangesAsync();
    #endregion

    #region 数据查询方法
    /// <summary>
    /// 非注入类型 TEntity 统一数据源查询入口, 可缓存数据源, 可返回泛型类型指定的任意类型引用
    /// </summary>
    /// <typeparam name="TEntitySet">查询的类型</typeparam>
    /// <returns></returns>
    public virtual IQueryable<TEntitySet> GetQueryable<TEntitySet>() where TEntitySet : class, IEntitySet<TKey>
    {
        var queryable = _context.Set<TEntitySet>();
        if (_enableCache)
        {
            var _type = typeof(TEntitySet);
            var cacheKey = AppSettings.CACHE_DATA + _type.Name.ToUpper(CultureInfo.CurrentCulture);
            var entitySets = _cache.GetValue<IEnumerable<TEntitySet>>(cacheKey);
            if (entitySets == null)
            {
                _logger.LogInformation("{type} have no cache data, will query and cached", _type.FullName);
                entitySets = queryable.ToHashSet();
                _cache.SetValue(cacheKey, entitySets);
            }

            return entitySets.AsQueryable();
        }

        return queryable;
    }
    /// <summary>
    /// 非注入类型 TEntity 统一数据源查询入口, 可缓存数据源, 可返回泛型类型指定的任意类型引用
    /// </summary>
    /// <typeparam name="TEntitySet">查询的类型</typeparam>
    /// <param name="predicate">过滤条件</param>
    /// <returns></returns>
    public virtual IQueryable<TEntitySet> GetQueryable<TEntitySet>(Expression<Func<TEntitySet, bool>> predicate) where TEntitySet : class, IEntitySet<TKey> => GetQueryable<TEntitySet>().Where(predicate);
    /// <summary>
    /// 根据 id 获取对象
    /// </summary>
    /// <typeparam name="TEntitySet">查询的类型</typeparam>
    /// <param name="id"></param>
    /// <returns></returns>
    public virtual async Task<TEntitySet?> GetAsync<TEntitySet>(TKey id) where TEntitySet : class, IEntitySet<TKey> => await GetQueryable<TEntitySet>().FirstOrDefaultAsync(x => x.Id.Equals(id));
    /// <summary>
    /// 从 QueryOptions 和上下文自动获取查询表达式的内容并执行查询返回分页的结果
    /// 暂不支持 orderby, select 和 expand 表达式
    /// </summary>
    /// <returns></returns>
    public virtual async Task<PagedResult<TEntitySet>> GetPagedResultAsync<TEntitySet>() where TEntitySet : class, IEntitySet<TKey>
    {
        var queryable = GetQueryable<TEntitySet>();
        var queryOptions = new QueryOptions<TEntitySet>(new QueryOptionRawValue(_httpContext.Request.Query));
        if (queryOptions.Filter != null)
        {
            if (queryOptions.Filter is NestedFilterOption<TEntitySet> filter && filter.Filters != null && filter.Filters.Any())
            {
                //目前仅支持一级嵌套, 且顶级条件仅支持 and
                //var expression = string.Join(GetFilterLogic(filter.Logic ?? LogicType.And), filter.Filters.Select(x => GetFilterExpression(x)));
                queryable = queryable.Where(string.Join(filter.GetFilterLogic(), filter.Filters.Select(x => x.GetFilterExpression())));
            }
            else if ((!string.IsNullOrWhiteSpace(queryOptions.Filter.Field)) && (!string.IsNullOrWhiteSpace(queryOptions.Filter.Value)))
            {
                queryable = queryable.Where(queryOptions.Filter.GetFilterExpression(), queryOptions.Filter.FieldValue);
            }
        }

        var result = new PagedResult<TEntitySet>();
        if (queryOptions.Count.HasValue && queryOptions.Count.Value)
        {
            result.Total = await queryable.CountAsync();
        }

        if (queryOptions.Skip.HasValue && queryOptions.Skip.Value > 0)
        {
            queryable = queryable.Skip(queryOptions.Skip.Value);
        }

        if (queryOptions.Top.HasValue && queryOptions.Top.Value > 0)
        {
            queryable = queryable.Take(queryOptions.Top.Value);
            result.PageSize = queryOptions.Top.Value;
        }

        result.PageIndex = ((queryOptions.Skip ?? 1) - 1) / result.PageSize + 1;
        result.Rows = await queryable.ToListAsync();
        return result;
    }
    #endregion

    #region 数据操作方法
    /// <summary>
    /// 获取在更新复制属性值时需要忽略的字段配置
    /// </summary>
    /// <typeparam name="TEntitySet">查询的类型</typeparam>
    /// <returns></returns>
    public virtual TypeAdapterConfig GetAdapterConfig<TEntitySet>() where TEntitySet : class, IEntitySet<TKey>
    {
        var config = new TypeAdapterConfig();
        config.ForType<TEntitySet, TEntitySet>().Ignore(nameof(IEntitySet<TKey>.Id));
        if (typeof(TEntitySet) == typeof(IEntitySetWithCreate<TKey>))
        {
            config.ForType<TEntitySet, TEntitySet>().Ignore(nameof(IEntitySetWithCreate<TKey>.RowVersion), nameof(IEntitySetWithCreate<TKey>.CreatedBy), nameof(IEntitySetWithCreate<TKey>.CreatedOn));
        }

        return config;
    }
    /// <summary>
    /// 新增或修改阶段判断满足条件的数据是否存在, 存在则抛出异常, 不存在则执行基类预定义赋值操作
    /// </summary>
    /// <typeparam name="TEntitySet">查询的类型</typeparam>
    /// <param name="entitySet">业务数据</param>
    /// <returns></returns>
    /// <exception cref="Exception"></exception>
    public virtual async Task UniqueAsync<TEntitySet>(TEntitySet entitySet) where TEntitySet : class, IEntitySet<TKey>
    {
        var _type = typeof(TEntitySet);
        var typeName = _type.GetDisplayName();
        var propertyInfos = _type.GetProperties().Where(x => x.HasAttribute<UniqueAttribute>());
        if (propertyInfos.IsNotNullOrEmpty())
        {
            var index = 0;
            var keys = new List<string>();
            var values = new List<object>();
            var messages = new List<string>();
            if (entitySet.Id != null && !entitySet.Id.Equals(default))
            {
                keys.Add($"{nameof(entitySet.Id)} != @{index++}");
                values.Add($"{entitySet.Id}");
            }

            foreach (var propertyInfo in propertyInfos)
            {
                var value = propertyInfo.GetValue(entitySet);
                keys.Add($"{propertyInfo.Name} == @{index++}");
                values.Add($"{value}");
                messages.Add($"<{propertyInfo.GetDisplayName()}> = {value}");
            }

            var predicate = string.Join(" and ", keys);
            var exist = await GetQueryable<TEntitySet>().AnyAsync(predicate, values.ToArray());
            if (exist)
            {
                throw new Exception($"{typeName} 中 {messages.ToString<string>()} 的记录已经存在!");
            }
        }
    }
    /// <summary>
    /// 批量新增或修改数据对象模型到当前上下文, 但不提交
    /// </summary>
    /// <typeparam name="TEntitySet">查询的类型</typeparam>
    /// <param name="entitySets">业务数据</param>
    /// <returns></returns>
    public virtual async Task AddOrUpdateAsync<TEntitySet>(params TEntitySet[] entitySets) where TEntitySet : class, IEntitySet<TKey>
    {
        if (entitySets.Length > 0)
        {
            var adapterConfig = GetAdapterConfig<TEntitySet>();
            var ids = entitySets.Select(x => x.Id).Distinct();
            var entities = await GetQueryable<TEntitySet>().Where(x => ids.Contains(x.Id)).ToListAsync();
            foreach (var entitySet in entitySets)
            {
                await UniqueAsync(entitySet);

                var entity = entities.FirstOrDefault(x => x.Id.Equals(entitySet.Id));
                if (entity == null)
                {
                    Create(entitySet);
                    Update(entitySet);
                    await _context.AddAsync(entitySet);
                }
                else
                {
                    entitySet.Adapt(entity, adapterConfig);
                    Update(entity);
                    _context.Update(entity);
                }
            }
        }
    }
    /// <summary>
    /// 单个数据对象的新增, 自动验证重复
    /// </summary>
    /// <typeparam name="TEntitySet">业务数据类型</typeparam>
    /// <param name="entitySet">业务数据</param>
    /// <returns></returns>
    public virtual async Task<TEntitySet> AddAsync<TEntitySet>(TEntitySet entitySet) where TEntitySet : class, IEntitySet<TKey>
    {
        await UniqueAsync(entitySet);
        Create(entitySet);
        Update(entitySet);
        await _context.AddAsync(entitySet);

        if (AutoSaveChanges)
        {
            await SaveChangesAsync();
        }

        return entitySet;
    }
    /// <summary>
    /// 单个数据对象的更新, 自动验证重复
    /// </summary>
    /// <typeparam name="TEntitySet">业务数据类型</typeparam>
    /// <param name="entitySet">业务数据</param>
    /// <returns></returns>
    public virtual async Task<TEntitySet> UpdateAsync<TEntitySet>(TEntitySet entitySet) where TEntitySet : class, IEntitySet<TKey>
    {
        var entity = await GetAsync<TEntitySet>(entitySet.Id);
        if (entity == null)
        {
            throw new Exception($"{ typeof(TEntitySet).GetDisplayName()} 中当前记录不存在!");
        }

        await UniqueAsync(entitySet);
        entitySet.Adapt(entity, GetAdapterConfig<TEntitySet>());
        Update(entity);
        _context.Update(entity);

        if (AutoSaveChanges)
        {
            await SaveChangesAsync();
        }

        return entity;
    }
    /// <summary>
    /// 删除记录
    /// </summary>
    /// <typeparam name="TEntitySet">查询的类型</typeparam>
    /// <param name="id">业务数据主键</param>
    /// <returns></returns>
    public virtual async Task DeleteAsync<TEntitySet>(TKey id) where TEntitySet : class, IEntitySet<TKey>
    {
        var entitySet = await GetAsync<TEntitySet>(id);
        if (entitySet == null)
        {
            throw new Exception($"{typeof(TEntitySet).GetDisplayName()} 中当前记录不存在!");
        }

        _context.Remove(entitySet);

        if (AutoSaveChanges)
        {
            await SaveChangesAsync();
        }
    }
    #endregion
}

/// <summary>
/// 公共数据处理服务
/// </summary>
/// <typeparam name="TDbContext">数据库上下文</typeparam>
public class ServiceBase<TDbContext> : ServiceBase<TDbContext, string>, IServiceBase<TDbContext> where TDbContext : DbContext
{
    /// <summary>
    /// 构造方法
    /// </summary>
    /// <param name="logger">日志</param>
    /// <param name="context">数据库上下文</param>
    /// <param name="httpContextAccessor">Http上下文</param>
    /// <param name="cache">缓存</param>
    /// <param name="appSetting">配置</param>
    public ServiceBase(
        TDbContext context,
        ILogger<ServiceBase<TDbContext, string>> logger,
        IDistributedCache cache,
        IHttpContextAccessor httpContextAccessor,
        AppSetting appSetting
        ) : base(context, logger, cache, httpContextAccessor, appSetting) { }
}